﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.X509;

namespace TheClerk.Forms.Templates.raw.Envelope
{
    /// <summary>
    /// The Envelope structure itself, independent of library in use
    /// </summary>
    /// <remarks>
    /// Envelope is an X.509 Certificate structure with 4 extensions.
    /// OpaquePayload
    ///     (generated from {Payload}, generated from { Assertions, Commitments, Proofs, Postmarks } )
    /// StrongBox
    ///     (set of StrongBoxItem)
    /// Stamps
    ///     (set of Stamp : Assertion)
    /// CopyrightLicense
    ///     (natural-language)
    ///     (parsable)
    /// 
    /// Envelope must also be equipped with all the chops before it can seal.
    /// 
    /// Envelope must create StrongBox and pass it to OpaquePayload constructor
    /// </remarks>
    public class Envelope
    {
        // Our certificate generator
        X509V3CertificateGenerator Self;

        // The four main extensions in this structure
        OpaquePayload opaquePayload;
        StrongBox strongBox;
        Stamps stamps;
        CopyrightLicense License;

        public Envelope()
        {
            // We need to send OpaquePayload the StrongBox this Envelope uses
            strongBox = new StrongBox();
            opaquePayload = new OpaquePayload(strongBox);
            stamps = new Stamps();
            License = new CopyrightLicense();
        }

        /// <summary>
        /// Add a private key to the list of Signers
        /// </summary>
        /// <param name="equipment">the chop to equip</param>
        void EquipChop(Chop equipment)
        {
            opaquePayload.AddChop(equipment);
        }

        void AddRecipient(AsymmetricIdentity recipient)
        {
            opaquePayload.AddRecipient(recipient);
        }

        /// <summary>
        /// Add transparent, shell-visible data to the Envelope
        /// </summary>
        /// <param name="stamp">The transparent data to add</param>
        /// <remarks>
        /// This information must be considered more suspect than Assertions,
        /// as it's protected by only a single signature under a single
        /// algorithm which (pessimistically) will be broken at some
        /// point in the future.
        /// </remarks>
        void AddStamp(Stamp stamp)
        {
            this.stamps.Add(stamp);
        }

        /// <summary>
        /// Add content either as an Assertion or as a Stream
        /// </summary>
        /// <param name="content">Must be Assertion or readable System.IO.Stream</param>
        void AddContent(System.Object content)
        {
            if (content is Assertion)
            {
                this.opaquePayload.payload.AddAssertion(content as Assertion);
                return;
            }
            if (content is Stream && true == (content as Stream).CanRead)
            {
                // It's not an Assertion, so we need to make one out of it
                this.opaquePayload.payload.AddAssertion(new Assertion(content as Stream, new AssertionType()));
                return;
            }
            throw new ApplicationException("Envelope.AddContent: Must be Assertion " +
                                           "or readable System.IO.Stream");
        }

        /// <summary>
        /// Seal it all up, returns a System.IO.Stream
        /// </summary>
        /// <returns></returns>
        public byte[] Seal()
        {
            Self = new X509V3CertificateGenerator();
            // Generi-fill the static fields of the Envelope
            fillEnvelope();

            // We need the nonce keypair, but we shouldn't worry about the generation later.
            Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair keypair;
            {
                var eckpg = new Org.BouncyCastle.Crypto.Generators.ECKeyPairGenerator("EC");
                var genparm = new Org.BouncyCastle.Crypto.KeyGenerationParameters(
                    new Org.BouncyCastle.Security.SecureRandom(
                        new Org.BouncyCastle.Crypto.Prng.CryptoApiRandomGenerator()), 256);
                eckpg.Init(genparm);
                keypair = eckpg.GenerateKeyPair();
                Self.SetPublicKey(keypair.Public);
                opaquePayload.SetOuterChop(keypair);
            }

            // Extensions get created and serialized here
            // OpaquePayload
            Self.AddExtension(
                oid: new DerObjectIdentifier("1.3.6.1.4.1.22232.7.1.0.0"),
                critical: false,
                extensionValue: new DerOctetString(this.opaquePayload.ToAsn1Encodable().GetDerEncoded()));
            // StrongBox
            Self.AddExtension(
                oid: new DerObjectIdentifier("1.3.6.1.4.1.22232.7.1.0.1"),
                critical: false,
                extensionValue: strongBox.ToAsn1Encodable().GetDerEncoded());
            // Copyright Notices
            Self.AddExtension(
                oid: new DerObjectIdentifier("1.3.6.1.4.1.22232.7.1.0.2"),
                critical: true,
                extensionValue: this.License.ToAsn1Encodable().GetDerEncoded());
            // Stamps
            Self.AddExtension(
                oid: new DerObjectIdentifier("1.3.6.1.4.1.22232.7.1.0.3"),
                critical: false,
                extensionValue: stamps.ToAsn1Encodable().GetDerEncoded());

            return Self.Generate(keypair.Private).GetEncoded();
        }

        /// <summary>
        /// Obtain the sealed Envelope as a Stream
        /// </summary>
        /// <returns>Stream containing sealed Envelope</returns>
        internal Stream Serialize() { return new MemoryStream(Seal()); }

        public Asn1Encodable ToAsn1Encodable()
        {
            return Asn1Object.FromStream(Serialize());
        }

        /// <summary>
        /// Called by Seal() to get the generator set up
        /// </summary>
        private void fillEnvelope()
        {
            // TODO: get all aspects of the envelope set up.
            // set up standard fields
            Self.SetIssuerDN(new X509Name("CN=whatever,C=XX"));
            Self.SetSubjectDN(new X509Name("CN=whatever,C=XX"));
            Self.SetNotBefore(DateTime.UtcNow.AddYears(-1));
            Self.SetNotAfter(DateTime.UtcNow.AddYears(1));
            // TODO: this shouldn't be a constant
            Self.SetSerialNumber(new Org.BouncyCastle.Math.BigInteger("13"));
            Self.SetSignatureAlgorithm("ECDSAWITHSHA1");
        }


        internal void Add(Assertion a)
        {
            opaquePayload.AddAssertion(a);
        }
    }
}
